/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package Compilador.Sintatico;

import Compilador.Lexico.Tokens.Token;
import Compilador.Lexico.Tokens.TokensType;
import Compilador.Sintatico.ArvoreSintatica.ArvoreSintatica;
import Compilador.Sintatico.ArvoreSintatica.NoArvoreSintatica;
import Compilador.Sintatico.ArvoreSintatica.TypeNo;
import java.io.File;
import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Scanner;
import java.util.Stack;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.commons.collections.map.MultiKeyMap;

/**
 *
 * @author Rafa
 */
public class AnalisadorSintatico {

    private ArrayList<TokensType> operadoresUnarios;
    private ArrayList<TokensType> operadoresBinarios;
    private ArrayList<String> terminaisVazio;
    private HashSet<String> terminais;
    private HashSet<String> nTerminais;
    private MultiKeyMap tabelaPreditiva;
    private Stack pilha;
    private boolean valido;
    private ArrayList<Token> tokens;
    private int posicaoToken;
    private String tokenAtual;
    private Stack pilhaAuxiliar;
    private int posicaoTokenAux;
    private String apontadorAux;
    private boolean backTracing;
    private ArvoreSintatica arvoreSintatica;
    private ArvoreSintatica arvoreSintaticaAux;
    private String auxTokenAtual;

    public AnalisadorSintatico(ArrayList<Token> tokens) {
        this.tokens = tokens;
        this.carregaOperadores();
        this.nTerminais = new HashSet<String>();
        this.terminais = new HashSet<String>();
        this.tabelaPreditiva = new MultiKeyMap();
        this.terminaisVazio = new ArrayList<String>();
        this.pilhaAuxiliar = new Stack();
        this.carregaTerminaisVazios();
        this.pilha = new Stack();
        try {
            this.carregaTable();
            this.valido = true;
        } catch (FileNotFoundException ex) {
            Logger.getLogger(AnalisadorSintatico.class.getName()).log(Level.SEVERE, null, ex);
            this.valido = false;
        }

    }

    public ArvoreSintatica getArvoreSintatica() {
        return arvoreSintatica;
    }

    private void carregaTerminaisVazios() {
        this.terminaisVazio.add("EXPENTREPOPTIONSNVAR");
        this.terminaisVazio.add("PREFIXEXPEND");
    }

    private String nextToken() {

        Token token = this.tokens.get(posicaoToken);
        String representacaoToken;
        tokenAtual = token.getToken();
        if (token.getTipoToken() == TokensType.String) {
            representacaoToken = "String";
        } else if (token.getTipoToken() == TokensType.Number) {
            representacaoToken = "Number";
        } else if (token.getTipoToken() == TokensType.Identificador) {
            representacaoToken = "Name";
        } else {
            representacaoToken = token.getToken();
        }

        posicaoToken++;
        return representacaoToken;

    }

    private int[] getPosicaoToken() {
        int[] posicao = new int[2];
        posicao[0] = this.tokens.get(posicaoToken - 1).getLinha();
        posicao[1] = this.tokens.get(posicaoToken - 1).getColuna();
        return posicao;
    }

    //Empilha a produção e já insere os nós na árvore
    private void empilhaProducao(String producao) {
        String[] simbolos = producao.split(" ");
        ArrayList<NoArvoreSintatica> nos = new ArrayList<NoArvoreSintatica>();
        for (int count = simbolos.length - 1; count >= 0; count--) {
            if (!simbolos[count].equals("&")) {
                this.pilha.push(simbolos[count]);

                nos.add(criaNoArvoreSintatica(simbolos[count], arvoreSintatica.getNoAtual()));


            }
        }
        arvoreSintatica.insertNodes(nos);
        if (nos.isEmpty()) {

            arvoreSintatica.atualizaNoAtual((String) pilha.peek());
        }
    }

    private NoArvoreSintatica criaNoArvoreSintatica(String descricao, NoArvoreSintatica pai) {
        boolean terminal = terminais.contains(descricao);
        NoArvoreSintatica noArvoreSintatica = new NoArvoreSintatica();
        noArvoreSintatica.setNoFilhos(new ArrayList<NoArvoreSintatica>());
        noArvoreSintatica.setPai(pai);
        noArvoreSintatica.setTerminal(terminal);
        noArvoreSintatica.setNoDescricao(descricao);
        return noArvoreSintatica;

    }

    private String getTokenAtual() {
        return this.tokenAtual;
    }

    public boolean parser() throws SintaticoException {

        if (this.valido) {
            this.pilha.push("$");
            this.tokens.add(new Token("$", -1, -1, TokensType.FimCadeia));
            //Starter da gramática
            this.pilha.push("CHUNK");
            arvoreSintatica = new ArvoreSintatica(criaNoArvoreSintatica("CHUNK", null));
            String X, apontador;
            apontador = this.nextToken();
            boolean exception = false;
            do {
                X = String.valueOf(pilha.peek());
                System.out.println(pilha);

                if (this.terminais.contains(X) || X.equals("$")) {
                    if (X.equals(apontador)) {

                        if (!X.equals("$")) {
                            arvoreSintatica.atualizaNomeNo(this.getTokenAtual());
                            arvoreSintatica.atualizaPosicaoNo(this.getPosicaoToken());
                            if (apontador.equals("true") || apontador.equals("false")) {
                                arvoreSintatica.atualizaTypeNoAtual(TypeNo.Boolean);
                            } else if (apontador.equals("Name")) {
                                arvoreSintatica.atualizaTypeNoAtual(TypeNo.Var);
                            } else if (apontador.equals("Number")) {
                                arvoreSintatica.atualizaTypeNoAtual(TypeNo.Number);
                            } else if (apontador.equals("String")) {
                                arvoreSintatica.atualizaTypeNoAtual(TypeNo.String);
                            }
                        }

                        pilha.pop();
                        if (!pilha.isEmpty()) {
                            if (pilha.peek().equals("end")) {
                                System.out.println("here");
                            }
                            arvoreSintatica.atualizaNoAtual((String) pilha.peek());
                        }

                        System.out.println("Terminal Reconhecido: " + this.getTokenAtual());

                        if (posicaoToken < tokens.size()) {
                            apontador = this.nextToken();
                        }


                    } else {
                        if (backTracing) {
                            apontador = apontadorAux;
                            posicaoToken = posicaoTokenAux;
                            tokenAtual = auxTokenAtual;
                            pilha = pilhaAuxiliar;
                            arvoreSintatica = arvoreSintaticaAux;
                            this.empilhaProducao("( OPTEXPLIST ) PREFIXEXPEND");
                            backTracing = false;


                        } else {
                            throw new SintaticoException(tokens.get(posicaoToken - 1).getLinha(), tokens.get(posicaoToken - 1).getColuna(), getTokenAtual());

                        }
                    }
                } else {

                    //Tratamento da ambiguidade da Linguagem

                    if (X.equals("PREFIXEXPEND") && apontador.equals("(") && (posicaoToken + 1 < tokens.size()) && (tokens.get(posicaoToken + 1).getToken().equals(",") || this.operadoresBinarios.contains(tokens.get(posicaoToken + 1).getTipoToken()))) {
                        this.pilha.pop();
                        this.empilhaProducao("( OPTEXPLIST ) PREFIXEXPEND");

                    } else if (X.equals("PREFIXEXPEND") && apontador.equals("(") && !backTracing) {
                        backTracing = true;
                        apontadorAux = apontador;
                        posicaoTokenAux = posicaoToken;
                        pilhaAuxiliar = (Stack) pilha.clone();
                        auxTokenAtual = tokenAtual;
                        arvoreSintaticaAux = (ArvoreSintatica) arvoreSintatica.clone();



                    } else if (tabelaPreditiva.containsKey(X, apontador)) {

                        this.pilha.pop();

                        this.empilhaProducao(String.valueOf(tabelaPreditiva.get(X, apontador)));
                        //   System.out.println(String.valueOf(tabelaPreditiva.get(X, apontador)));
                    } else {
                        //Tratamento da ambiguidade da Linguagem
                        if (this.terminaisVazio.contains(X)) {
                            this.pilha.pop();
                        } else {
                            if (backTracing) {
                                apontador = apontadorAux;
                                posicaoToken = posicaoTokenAux;
                                tokenAtual = auxTokenAtual;
                                pilha = pilhaAuxiliar;
                                arvoreSintatica = arvoreSintaticaAux;
                                this.pilha.pop();
                                this.empilhaProducao("( OPTEXPLIST ) PREFIXEXPEND");

                                backTracing = false;
                            } else {

                                throw new SintaticoException(tokens.get(posicaoToken - 1).getLinha(), tokens.get(posicaoToken - 1).getColuna(), getTokenAtual());

                            }
                        }
                    }
                }
            } while (!X.equals("$") && !exception);
            return true;
        }
        return false;

    }

    private void carregaOperadores() {
        operadoresUnarios = new ArrayList<TokensType>();
        operadoresBinarios = new ArrayList<TokensType>();
        //Adiciona os operadores Unarios
        operadoresUnarios.add(TokensType.Subtracao);
        operadoresUnarios.add(TokensType.Not);
        operadoresUnarios.add(TokensType.Sharp);
        //Adiciona os operadores Binarios
        operadoresBinarios.add(TokensType.Adicao);
        operadoresBinarios.add(TokensType.Subtracao);
        operadoresBinarios.add(TokensType.Multiplicacao);
        operadoresBinarios.add(TokensType.Divisao);
        operadoresBinarios.add(TokensType.Exponencial);
        operadoresBinarios.add(TokensType.Modulo);
        operadoresBinarios.add(TokensType.PontoDuplo);
        operadoresBinarios.add(TokensType.LT);
        operadoresBinarios.add(TokensType.LE);
        operadoresBinarios.add(TokensType.GT);
        operadoresBinarios.add(TokensType.GE);
        operadoresBinarios.add(TokensType.EQ);
        operadoresBinarios.add(TokensType.Diferente);
        operadoresBinarios.add(TokensType.And);
        operadoresBinarios.add(TokensType.Or);

    }

    private void carregaTable() throws FileNotFoundException {
        ArrayList<String> linhasLidas = this.leArquivo();
        for (int count = 0; count < linhasLidas.size(); count++) {
            this.leLinha(linhasLidas.get(count));
        }


    }

    private void leLinha(String linha) {
        String[] linhaQuebrada = linha.split("::=");
        String nTerminal = linhaQuebrada[0].trim();
        this.nTerminais.add(nTerminal);
        if (linhaQuebrada.length > 1) {
            String[] producoes = linhaQuebrada[1].split("!");
            for (int count = 0; count < producoes.length - 1; count++) {
                String[] composicao = producoes[count].split("-->");
                String terminal = composicao[0].replace("\"", "").trim();
                String producao = composicao[1].replace("\"", "").trim();
                this.terminais.add(terminal);
                if (this.tabelaPreditiva.containsKey(nTerminal, terminal)) {
                    System.out.println(nTerminal + "-" + terminal + "-" + producao);
                }
                this.tabelaPreditiva.put(nTerminal, terminal, producao);
            }

        }


    }

    private ArrayList<String> leArquivo() throws FileNotFoundException {
        Scanner scan = new Scanner(new File("tabelaGrammar.txt"));
        ArrayList<String> linhasLidas = new ArrayList<String>();
        while (scan.hasNextLine()) {
            linhasLidas.add(scan.nextLine());
        }
        return linhasLidas;
    }
}
